/*
 * Created on Feb 13, 2008
 */
package maxygen;

import java.io.*;

import com.cycling74.max.*;

/**
 * A wrapper for a <code>RawWriter</code> which in turn wraps a <code>FileWriter</code> and
 * a <code>BufferedWriter</code>. The intention of this class is to handle the Doxygen specific
 * formatting and HTML. The <code>RawWriter</code> should deal with the layout of the plain text
 * document (e.g., number of chars per line)
 * 
 * @author martinrobinson
 */
class DoxyWriter {
    
    
    
    private engine e;
    private PatcherWrapper p;
    private String path;
    private String anchorPrefix;
    private FileWriter fileWriter;
    private BufferedWriter bufferedWriter;
    private RawWriter o;
    private String lastCommand = "";
    
    private boolean doneElementTableHeader;
    
    static final String HTML_NBSP = "&nbsp;";
    static final String HTML_BR = "<br>";
    static final String HTML_OPENCODE = "<code>";
    static final String HTML_CLOSECODE = "</code>";
    static final String HTML_OPENSTRONG = "<strong>";
    static final String HTML_CLOSESTRONG = "</strong>";
    static final String HTML_OPENEM = "<em>";
    static final String HTML_CLOSEEM = "</em>";
    
    static final String NOTDOCUMENTED = HTML_NBSP;//"<em>not documented</em>";
    static final String NOTSPECIFIED = HTML_NBSP;//"<em>(type not specified)</em>";
    
    /*
    static final String FILE_NEWLINE = "\n\t";
    static final String FILE_NEWPARA = "\n"+FILE_NEWLINE;
    */
    static final String FILE_NEWLINE = "\n";//"\n\t"
    static final String FILE_NEWPARA = "\n"+FILE_NEWLINE;
    static final String OPEN_LITERAL = "(?<!\\{\\{)";
    static final String CLOSE_LITERAL = "(?<!\\}\\})";
    
    
    private static final int MINUSONE = -1;

    /**
     * Creat a new DoxyWriter
     * @param e     the maxygen.engine using this DoxyWriter
     */
    DoxyWriter(engine e)
    {
        try {
            this.e = e;
            p = new PatcherWrapper(e);
            path = p.getPath()+p.getName()+e.getAttrString("DOCFILE_SUFFIX");
            anchorPrefix = StringUtility.underscoreNonAlphaNum(p.getName())+"_";
            doneElementTableHeader = false;
            
            fileWriter = new FileWriter(path);
            bufferedWriter = new BufferedWriter(fileWriter);
            o = new RawWriter(bufferedWriter, 80, 4);
        }
        catch(Exception ex) {
            clear();
            ex.printStackTrace();
        }
    }
    
    /**
     * Get the HTML anchor prefix for this writer
     * @return  the anchor prefix string
     */
    String getAnchorPrefix()
    {
        return anchorPrefix;
    }
    
    /**
     * Zero all internal objects
     */
    private void clear()
    {
        e = null;
        p = null;
        path = null;
        fileWriter = null;
        bufferedWriter = null;
    }
    
    /**
     * Close the writer and zero all internal objects
     * @throws Exception    file errors
     */
    void close() throws Exception
    {
        o.close();
        clear();
    }
    
    /**
     * @return Gets current maxygen.engine for this writer
     */
    engine getEngine()
    {
        return e;
    }
    
    /**
     * @return  Gets the file path of the current documentation file being written
     */
    String getPath()
    {
        return path;
    }
    
    /**
     * Write a new line to the DoxyWriter
     * @throws Exception    file write errors
     */
    void ln() throws Exception
    {
        o.write("\n"); /* new line */
    }
    
    /**
     * Write a new paragraph to the DoxyWriter
     * @throws Exception    file write errors
     */
    void pr() throws Exception
    {
        o.write("\n\n"); /* new paragraph */
    }
    
    /**
     * Write a tab to the DoxyWriter
     * @throws Exception    file write errors
     */
    void tab() throws Exception
    {
        o.write("\t"); /* tab */
    }
    
    /**
     * Write the documentation file header to the DoxyWriter
     * @throws Exception    file write errors
     */
    void writeHeader() throws Exception
    {
        lastCommand = "writeHeader";
        o.write("/*! @file \"");
        o.write(p.getName());
        o.write("\"");
        ln();
    }
    
    /**
     * Write the documentation file footer to the DoxyWriter
     * @throws Exception    file write errors
     */
    void writeFooter() throws Exception
    {
        lastCommand = "writeFooter";
        o.write("*/");
    }
    
    /**
     * Write a horizontal ruler to the documentation file
     * @throws Exception    file write errors
     */
    void writeRuler() throws Exception
    {
        if(!lastCommand.equals("writeRuler")) {
            lastCommand = "writeRuler";
            o.write("\n<hr>\n");
        }
    }
    
    /**
     * Write an element table header to the documentation file.
     * <p>
     * Element tables are for documenting Attributes, Arguments Messages, 
     * Inlets and Outlets. This formatting is copied form the formatting used
     * by Doxygen for C++ class member documentation (although as this is HTML it 
     * doesn't transfer well to Latex and RTF output).
     * 
     * @param header        The heading to use
     * @throws Exception    file write errors
     */
    void writeElementTableHeader(String header) throws Exception
    {
        lastCommand = "writeElementTableHeader";
        writeElementTableHeader(header, null);
    }
    
    /**
     * Write an element table header to the documentation file with an HTML anchor.
     * <p>
     * Element tables are for documenting Attributes, Arguments Messages, 
     * Inlets and Outlets. This formatting is copied form the formatting used
     * by Doxygen for C++ class member documentation (although as this is HTML it 
     * doesn't transfer well to Latex and RTF output).
     * 
     * @param header        The heading to use
     * @param anchor        The HTML anchor to use for this header
     * @throws Exception    file write errors
     */
    void writeElementTableHeader(String header, String anchor) throws Exception
    {
        /*
         * should do the <table> stuff only once for attributes, inlets and outlets
         * this would keep everything aligned
         */
        lastCommand = "writeElementTableHeader";
        if(!doneElementTableHeader) {
            o.write("<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\">");
            o.write("<tr><td></td></tr>");
            
            doneElementTableHeader = true;
        }
        
        o.write("<tr><td colspan=\"2\"><br><h2>");
        if(anchor == null || anchor.length() == 0)
            anchor = StringUtility.underscoreWhitespace(header);
        else
            anchor = StringUtility.underscoreWhitespace(anchor);
        o.write("@anchor "+anchorPrefix+anchor+" ");
        o.write(header);
        ln();
        o.write("</h2></td></tr>");
        pr();
            
            
    }
    
    /**
     * Write an element table footer to the documentation file with an HTML anchor.
     * <p>
     * This doesn't write a footer if a corresponding header has not been written.
     * @throws Exception    file write errors
     */
    void writeElementTableFooter() throws Exception
    {
        lastCommand = "writeElementTableFooter";
        if(doneElementTableHeader) {
            o.write("</table>");
            pr();
            
            doneElementTableHeader = false;
        }
    }
    
    void writeElementTableItem(String rightbottom) throws Exception
    {
        String table[] = new String[]{HTML_NBSP, HTML_NBSP, HTML_NBSP, rightbottom};
        writeElementTableItem(table, null);
    }
    
    void writeElementTableItem(String lefttop, String rightbottom) throws Exception
    {
        String table[] = new String[]{lefttop, HTML_NBSP, HTML_NBSP, rightbottom};
        writeElementTableItem(table, null);
    }
    
    void writeElementTableItem(String lefttop, String leftbottom, String rightbottom) throws Exception
    {
        String table[] = new String[]{lefttop, HTML_NBSP, leftbottom, rightbottom};
        writeElementTableItem(table, null);
    }
    
    void writeElementTableItem(
            String lefttop, String righttop, 
            String leftbottom, String rightbottom) throws Exception
    {
        String table[] = new String[]{lefttop, righttop, leftbottom, rightbottom};
        writeElementTableItem(table, null);
    }
    
    void writeElementTableItem(String table[], String anchor) throws Exception
    {
        
        switch(table.length) {
        case 0:
            writeElementTableItem(HTML_NBSP);
            return;
        case 1:
            writeElementTableItem(table[0]);
            return;
        case 2:
            writeElementTableItem(table[0], table[1]);
            return;
        case 3:
            writeElementTableItem(table[0], table[1], table[2]);
            return;
        default:
            lastCommand = "writeElementTableItem";
            o.write("<tr><td class=\"memItemLeft\" nowrap align=\"right\" valign=\"top\">");
            
            if(anchor!=null && anchor.length() > 0)
                o.write("@anchor "+anchorPrefix+anchor+" \n");//\t\t");
            
            o.write(table[0]);
            o.write("</td>");
            ln();
            
            o.write("<td class=\"memItemRight\" valign=\"bottom\">");
            o.write(table[1]);
            o.write("</td></tr>");
            ln();
            
            o.write("<tr><td class=\"mdescLeft\" align=\"right\" valign=\"top\">");
            o.write(table[2]);
            o.write("</td>");
            ln();
            
            o.write("<td class=\"mdescRight\">");
            o.write(table[3]);
            o.write("</td></tr>");
            
            pr();
        }
    }
    
    void writeElementTableSingleLine(String table[], String anchor) throws Exception
    {
        if(table.length != 2) return;
        
        lastCommand = "writeElementTableSingleLine";
        
        o.write("<tr><td class=\"memTemplItemLeft\" nowrap align=\"right\" valign=\"top\">");
        
        if(anchor!=null && anchor.length() > 0)
            o.write("@anchor "+anchorPrefix+anchor+" \n");//\t\t");
        
        o.write(table[0]);
        o.write("</td>");
        ln();
        
        o.write("<td class=\"memTemplItemRight\" valign=\"bottom\">");
        o.write(table[1]);
        o.write("</td></tr>");
        ln();
    }
    
    void writeLockedImageInfo() throws Exception
    {
        writeRuler();
        lastCommand = "writeLockedImageInfo";
        String name = p.getName();
        pr();
        o.write("@anchor ");
        o.write(anchorPrefix+StringUtility.underscoreNonAlphaNum(e.getAttrString("captionlocked")));
        ln();
        o.write("@image html \"");
        o.write(name+e.getAttrString("IMAGEFILE_SUFFIX"));
        o.write("\" \"");
        o.write(e.getAttrString("captionlocked"));
        o.write(": ");
        o.write(name);
        o.write(" patch\"");
        ln();
    }
    
    void writeUnlockedImageInfo() throws Exception
    {
        writeRuler();
        lastCommand = "writeUnlockedImageInfo";
        String name = p.getName();
        pr();
        o.write("@anchor ");
        o.write(anchorPrefix+StringUtility.underscoreNonAlphaNum(e.getAttrString("captionunlocked")));
        ln();
        o.write("@image html \"");
        o.write(name+e.getAttrString("UNLOCKEDFILE_QUALIFIER")+e.getAttrString("IMAGEFILE_SUFFIX"));
        o.write("\" \"");
        o.write(e.getAttrString("captionunlocked"));
        o.write(": ");
        o.write(name);
        o.write(" patch (edit mode)\"");
        ln();
    }
    
    void writeLockedRegions() throws Exception
    {
        lastCommand = "writeLockedRegions";
        e.lockedRegions = e.getRegions(Region.LOCKED);
        
        // no header
        for(int i = 0; i < e.lockedRegions.length; i++) {
            writeOneRegion(e.lockedRegions[i]);
        }
    }
    
    void writeUnlockedRegions() throws Exception
    {
        lastCommand = "writeUnlockedRegions";
        e.unlockedRegions = e.getRegions(Region.UNLOCKED);
        
        // no header
        for(int i = 0; i < e.unlockedRegions.length; i++) {
            writeOneRegion(e.unlockedRegions[i]);
        }
    }
    
    void writeOneRegion(Region region) throws Exception
    {
        writeRuler();
        lastCommand = "writeOneRegion";
        String name = p.getName();
        pr();
        o.write("@anchor ");
        o.write(anchorPrefix+region.getAnchor());
        ln();
        o.write("@image html \"");
        o.write(name+"_"+region.name+e.getAttrString("IMAGEFILE_SUFFIX"));
        o.write("\" \"");
        o.write(region.getBrief());
        o.write("\"");
        pr();
        o.write(region.getDetail());
        pr();
    
    }
    
    void writeArbitraryCommand(String cmd, boolean inviscomments) throws Exception
    {        
        /*
         * need to make this more sophisticated, checking the type of command...
         * this just pastes in the text according to the command from the script
         * 
         * only works with comment type objects at present, would be really useful with regions
         * for example....
         * 
         * probably best to do this parsing in engine.java 
         * it will be similar to engine.executeScriptCommand() if..if else.. etc
         */
        lastCommand = "writeArbitraryCommand";
        String comment = e.getTaggedComment(cmd, inviscomments);
        
        if(comment != null) {
            o.write(comment);
            pr();
        }
    }
    
    void writeBrief() throws Exception
    {        
        lastCommand = "writeBrief";
        String brief = e.getBrief();
        
        if(brief != null) {
            o.write("@brief ");
            o.write(brief);
            pr();
        }
    }
    
    void writeVersion() throws Exception
    {
        lastCommand = "writeVersion";
        String version = e.getVersion();
        
        if(version != null) {
            o.write("@version ");
            o.write(version);
            pr();
        }
    }
    
    void writeDate() throws Exception
    {
        lastCommand = "writeDate";
        String date = e.getSetDate();
        
        if(date != null) {
            o.write("@date ");
            o.write(date);
            pr();
        }
    }
    
    void writeDetails() throws Exception
    {
        lastCommand = "writeDetails";
        String details[] = e.getDetails();
        
        // no header
        for(int i = 0; i < details.length; i++) {
            writeOneDetail(details[i]);
        }
     
    }
    
    void writeOneDetail(String detail) throws Exception
    {
        lastCommand = "writeOneDetail";
        o.write(detail);
        pr();
    }
    
    void writeBugs() throws Exception
    {
        lastCommand = "writeBugs";
        String bugs[] = e.getBugs();
        
        if(bugs.length > 0)
            writeBugsHeader();
        
        for(int i = 0; i < bugs.length; i++) {
            writeOneBug(bugs[i]);
        }
    }
    
    void writeBugsHeader() throws Exception
    {
        //o.write("Bugs:");
        //pr(o);
    }
    
    void writeOneBug(String bug) throws Exception
    {
        lastCommand = "writeOneBug";
        o.write("@bug ");
        o.write(bug);
        pr();
    }
    
    void writeWarnings() throws Exception
    {
        lastCommand = "writeWarnings";
        String warnings[] = e.getWarnings();
                
        for(int i = 0; i < warnings.length; i++) {
            writeOneWarning(warnings[i]);
        }
    }
    
    void writeWarningsHeader() throws Exception
    {
        lastCommand = "writeWarningsHeader";
        o.write("Warnings:");
        pr();
    }
    
    void writeOneWarning(String warning) throws Exception
    {
        lastCommand = "writeOneWarning";
        o.write("@warning ");
        o.write(warning);
        pr();
    }
    
    void writeAuthors() throws Exception
    {
        lastCommand = "writeAuthors";
        String authors[] = e.getAuthors();
                
        for(int i = 0; i < authors.length; i++) {
            writeOneAuthor(authors[i]);
        }
    }
    
    void writeAuthorsHeader() throws Exception
    {
        lastCommand = "writeAuthorsHeader";
        o.write("Author:");
        pr();
    }
    
    void writeOneAuthor(String author) throws Exception
    {
        lastCommand = "writeOneAuthor";
        o.write("@author ");
        o.write(author);
        pr();
    }
    
    void writeTodos() throws Exception
    {
        lastCommand = "writeTodos";
        String todos[] = e.getTodos();
        
        for(int i = 0; i < todos.length; i++) {
            writeOneTodo(todos[i]);
        }
    }
    
    void writeTodosHeader() throws Exception
    {
        lastCommand = "writeTodosHeader";
        o.write("Todo:");
        pr();
    }
    
    void writeOneTodo(String todo) throws Exception
    {
        lastCommand = "writeOneTodo";
        o.write("@todo ");
        o.write(todo);
        pr();
    }
    
    void writeArguments() throws Exception
    {
        lastCommand = "writeArguments";
        String args[] = e.getArguments();
        
        if(args.length > 0)
            writeArgumentsHeader();
        
        for(int i = 0; i < args.length; i++) {
            writeOneArgument(args[i]);
        }
    }
    
    void writeArgumentsHeader() throws Exception
    {
        lastCommand = "writeArgumentsHeader";
        writeElementTableHeader("Arguments"); 
    }
    
    void writeOneArgument(String arg) throws Exception
    {        
        lastCommand = "writeOneArgument";
        String argList = parseArgText(arg);
                
        String split[] = parseArgList(argList);
        
        for(int i = 0; i < split.length; i++) {
            String oneArg[] = parseOneArg(split[i]);
            String anchor = "Arguments_"+
                StringUtility.underscoreNonAlphaNum(oneArg[0].trim().split(" ")[0]);
            
            /* need to deal with with (optional) construct perhaps put this in the description */
            
            oneArg[0] = addStrongTag(oneArg[0]);
            writeElementTableSingleLine(oneArg, anchor);
        }
    }
    
    void writeAttributes() throws Exception
    {
        lastCommand = "writeAttributes";
        String attrs[] = e.getAttributes();
        
        if(attrs.length > 0)
            writeAttributesHeader();
        
        for(int i = 0; i < attrs.length; i++) {
            writeOneAttribute(attrs[i]);
        }
    }
    
    void writeAttributesHeader() throws Exception
    {
        lastCommand = "writeAttributesHeader";
        writeElementTableHeader("Attributes"); 
    }
    
    void writeOneAttribute(String attr) throws Exception
    {
        lastCommand = "writeOneAttribute";
        String split[];
        
        split = parseAttrText(attr);
        
        if(split.length != 4) {
            if(split.length > 0)
                split[0] = addStrongTag(split[0]);
            writeElementTableItem(split, null); // no anchor?
            return;
        }
        
        String anchor = "Attributes_"+split[0];
        
        //if(split.length > 0) redundant or needs checking somehow else
            split[0] = addStrongTag(split[0]);
        
        String attrType = split[1];
        String attrOptions = split[2];
        split[2] = HTML_NBSP;
        
        writeElementTableItem(split, anchor);
        
        split = parseAttrOptions(attrOptions);
        
        if(attrType.equals(NOTSPECIFIED)||attrType.contains("list")) {
            for(int i = 0; i < split.length; i++) {
                // treat as an argument except don't make the 1st, 2nd etc bold
                String oneArg[] = parseOneArg(split[i]); 
                               
                writeElementTableSingleLine(oneArg, null);
            }
        } else {
            for(int i = 0; i < split.length; i++) {
                String option[] = parseOneAttrOption(split[i]);
                            
                if(!(option[0].equals(DoxyWriter.HTML_NBSP) && option[1].equals(DoxyWriter.HTML_NBSP)) ) 
                    writeElementTableSingleLine(option, null);
            }
        }
    }
    
    void writeMessages() throws Exception
    {
        lastCommand = "writeMessages";
        String messages[] = e.getMessages();
        
        if(messages.length > 0)
            writeMessagesHeader();
        
        for(int i = 0; i < messages.length; i++) {
            writeOneMessage(messages[i]);
        }
    }
    
    void writeMessagesHeader() throws Exception
    {
        lastCommand = "writeMessagesHeader";
        writeElementTableHeader("Messages"); 
    }
    
    void writeOneMessage(String message) throws Exception
    {
        lastCommand = "writeOneMessage";
        String split[];
        
        split = parseMessageText(message);

        if(split.length != 4) {
            writeElementTableItem(split, null); // no anchor
            return;
        }
        
        String anchor = "Messages_"+split[0];
        
        split[0] = addStrongTag(split[0]);
        
        String messageType = split[1];
        String messageOptions = split[2];
        split[2] = HTML_NBSP;
        
        writeElementTableItem(split, anchor); // anchor here
        
        split = parseMessageOptions(messageOptions);
                
        if(messageType.equals(NOTSPECIFIED)||messageType.contains("list")) {
            for(int i = 0; i < split.length; i++) {
             // treat as an argument except don't make the 1st, 2nd etc bold
                String oneArg[] = parseOneArg(split[i]); 
                                
                writeElementTableSingleLine(oneArg, null);
            }
        } else {
            for(int i = 0; i < split.length; i++) {
                String option[] = parseOneMessageOption(split[i]);
                                
                if(!(option[0].equals(DoxyWriter.HTML_NBSP) && option[1].equals(DoxyWriter.HTML_NBSP)) ) {
                    writeElementTableSingleLine(option, null);
                }
            }
        }
    }
    
    void writeInlets() throws Exception
    {
        lastCommand = "writeInlets";
        String inlets[] = e.getInlets();
        
        if(inlets.length == 0) return;
             
        writeInletsHeader();
        
        for(int i = 0; i < inlets.length; i++) {
            writeOneInlet(inlets[i], i == inlets.length-1 ? (i*MINUSONE) : i);
        }
        
        writeInletsFooter();
    }
    
    void writeOneInlet(String inlet, int index) throws Exception
    {   
        lastCommand = "writeOneInlet";
        String items[] = inletTextFromIndex(index, inlet);
        String anchor = StringUtility.underscoreWhitespace(items[0].trim());
        items[0] = addStrongTag(items[0]);
        
        writeElementTableItem(items, anchor);
    }    
    
    void writeInletsHeader() throws Exception
    {  
        lastCommand = "writeInletsHeader";
        writeElementTableHeader("Inlets");   
    }
    
    void writeInletsFooter() throws Exception
    {
        //writeElementTableFooter();
    }
    
    void writeOutlets() throws Exception
    {
        lastCommand = "writeOutlets";
        String outlets[] = e.getOutlets();
        
        if(outlets.length == 0) return;
            
        writeOutletsHeader();
        
        for(int i = 0; i < outlets.length; i++) {
            writeOneOutlet(outlets[i], i == outlets.length-1 ? (i*MINUSONE) : i);
        }
        
        writeOutletsFooter();
    }
    
    void writeOneOutlet(String outlet, int index) throws Exception
    {
        lastCommand = "writeOneOutlet";
        String items[] = outletTextFromIndex(index, outlet);
        String anchor = StringUtility.underscoreWhitespace(items[0].trim());
        items[0] = addStrongTag(items[0]);
        
        writeElementTableItem(items, anchor);
    }
        
    void writeOutletsHeader() throws Exception
    {
        lastCommand = "writeOutletsHeader";
        writeElementTableHeader("Outlets"); 
    }
    
    void writeOutletsFooter() throws Exception
    {
        //writeElementTableFooter();
    }
    
    void writePolyInlets() throws Exception
    {
        lastCommand = "writePolyInlets";
        String inlets[] = e.getPolyInlets();
        
        if(inlets.length == 0) return;
             
        writePolyInletsHeader();
        
        for(int i = 0; i < inlets.length; i++) {
            writeOnePolyInlet(inlets[i], i == inlets.length-1 ? (i*MINUSONE) : i);
        }
        
        writePolyInletsFooter();
    }
    
    void writeOnePolyInlet(String inlet, int index) throws Exception
    {   
        lastCommand = "writeOnePolyInlet";
        String items[] = polyInletTextFromIndex(index, inlet);
        String anchor = StringUtility.underscoreWhitespace(items[0].trim());
        items[0] = addStrongTag(items[0]);
        
        writeElementTableItem(items, anchor);
    }
    
    void writePolyInletsHeader() throws Exception
    {  
        lastCommand = "writePolyInletsHeader";
        writeElementTableHeader("Inlets (<code>in</code>)", "Inlets_in");   
    }
    
    void writePolyInletsFooter() throws Exception
    {
        //writeElementTableFooter();
    }
    
    void writePolyOutlets() throws Exception
    {
        lastCommand = "writePolyOutlets";
        String outlets[] = e.getPolyOutlets();
        
        if(outlets.length == 0) return;
             
        writePolyOutletsHeader();
        
        for(int i = 0; i < outlets.length; i++) {
            writeOnePolyOutlet(outlets[i], i == outlets.length-1 ? (i*MINUSONE) : i);
        }
        
        writePolyOutletsFooter();
    }
    
    void writeOnePolyOutlet(String outlet, int index) throws Exception
    {   
        lastCommand = "writeOnePolyOutlet";
        String items[] = polyOutletTextFromIndex(index, outlet);
        String anchor = StringUtility.underscoreWhitespace(items[0].trim());
        items[0] = addStrongTag(items[0]);
        
        writeElementTableItem(items, anchor);
    }    
    
    void writePolyOutletsHeader() throws Exception
    {  
        lastCommand = "writePolyOutletsHeader";
        writeElementTableHeader("Outlets (<code>out</code>)", "Outlets_out");   
    }
    
    void writePolyOutletsFooter() throws Exception
    {
        //writeElementTableFooter();
    }
    
    void writePolySignalInlets() throws Exception
    {
        lastCommand = "writePolySignalInlets";
        String inlets[] = e.getPolySignalInlets();
        
        if(inlets.length == 0) return;
             
        writePolySignalInletsHeader();
        
        for(int i = 0; i < inlets.length; i++) {
            writeOnePolySignalInlet(inlets[i], i == inlets.length-1 ? (i*MINUSONE) : i);
        }
        
        writePolySignalInletsFooter();
    }
    
    void writeOnePolySignalInlet(String inlet, int index) throws Exception
    {   
        lastCommand = "writeOnePolySignalInlet";
        String items[] = polySignalInletTextFromIndex(index, inlet);
        String anchor = StringUtility.underscoreWhitespace(items[0].trim());
        items[0] = addStrongTag(items[0]);
        
        writeElementTableItem(items, anchor);
    }    
    
    void writePolySignalInletsHeader() throws Exception
    {  
        lastCommand = "writePolySignalInletsHeader";
        writeElementTableHeader("Signal inlets (<code>in~</code>)", "Inlets_in_");   
    }
    
    void writePolySignalInletsFooter() throws Exception
    {
        //writeElementTableFooter();
    }
    
    void writePolySignalOutlets() throws Exception
    {
        lastCommand = "writePolySignalOutlets";
        String outlets[] = e.getPolySignalOutlets();
        
        if(outlets.length == 0) return;
             
        writePolySignalOutletsHeader();
        
        for(int i = 0; i < outlets.length; i++) {
            writeOnePolySignalOutlet(outlets[i], i == outlets.length-1 ? (i*MINUSONE) : i);
        }
        
        writePolySignalOutletsFooter();
    }
    
    void writeOnePolySignalOutlet(String outlet, int index) throws Exception
    {   
        lastCommand = "writeOnePolySignalOutlet";
        String items[] = polySignalOutletTextFromIndex(index, outlet);
        String anchor = StringUtility.underscoreWhitespace(items[0].trim());
        items[0] = addStrongTag(items[0]);
        
        writeElementTableItem(items, anchor);
    }    
    
    void writePolySignalOutletsHeader() throws Exception
    {  
        lastCommand = "writePolySignalOutletsHeader";
        writeElementTableHeader("Signal outlets (<code>out~</code>)", "Outlets_out_");   
    }
    
    void writePolySignalOutletsFooter() throws Exception
    {
        //writeElementTableFooter();
    }
    
    void writeAllPolyOutlets() throws Exception
    {
        lastCommand = "writeAllPolyOutlets";
        IOLet outlets[] = e.getAllPolyOutlets();
        
        if(outlets.length == 0) return;
             
        writeAllPolyOutletsHeader();
        
        for(int i = 0; i < outlets.length; i++) {
            if(outlets[i].type == IOLet.Type.POLYSIGNALOUTLET)
                writeOnePolySignalOutlet(outlets[i].comment, i == outlets.length-1 ? (i*MINUSONE) : i);
            else if(outlets[i].type == IOLet.Type.POLYNORMALOUTLET)
                writeOnePolyOutlet(outlets[i].comment, i == outlets.length-1 ? (i*MINUSONE) : i);
        }
        
        writeAllPolyOutletsFooter();
    }    
    
    void writeAllPolyOutletsHeader() throws Exception
    {  
        lastCommand = "writeAllPolyOutletsHeader";
        writeElementTableHeader("Outlets (<code>out/out~</code>)", "Outlets_out_out_");   
    }
    
    void writeAllPolyOutletsFooter() throws Exception
    {
        //writeElementTableFooter();
    }
    
    void writeAllPolyInlets() throws Exception
    {
        lastCommand = "writeAllPolyInlets";
        IOLet inlets[] = e.getAllPolyInlets();
        
        if(inlets.length == 0) return;
             
        writeAllPolyInletsHeader();
        
        for(int i = 0; i < inlets.length; i++) {
            if(inlets[i].type == IOLet.Type.POLYCOMBINEDINLET)
                writeOnePolyCombinedInlet(inlets[i].comment, i == inlets.length-1 ? (i*MINUSONE) : i);
            else if(inlets[i].type == IOLet.Type.POLYSIGNALINLET)
                writeOnePolySignalInlet(inlets[i].comment, i == inlets.length-1 ? (i*MINUSONE) : i);
            else if(inlets[i].type == IOLet.Type.POLYNORMALINLET)
                writeOnePolyInlet(inlets[i].comment, i == inlets.length-1 ? (i*MINUSONE) : i);
        }
        
        writeAllPolyInletsFooter();
    }
    
    void writeOnePolyCombinedInlet(String inlet, int index) throws Exception
    {   
        lastCommand = "writeOnePolyCombinedInlet";
        String items[] = polyCombinedInletTextFromIndex(index, inlet);
        String anchor = StringUtility.underscoreWhitespace(items[0].trim());
        items[0] = addStrongTag(items[0]);
        
        writeElementTableItem(items, anchor);
    }    
    
    void writeAllPolyInletsHeader() throws Exception
    {  
        lastCommand = "writeAllPolyInletsHeader";
        writeElementTableHeader("Inlets (<code>in/in~</code>)", "Inlets_in_in_");   
    }
    
    void writeAllPolyInletsFooter() throws Exception
    {
        //writeElementTableFooter();
    }
    
    private String[] inletTextFromIndex(int index, String description)
    { 
        String split[] = StringUtility.splitIOLetTypeDesc(description);
        
        if(index == 0) 
            return new String[]{
                ("Inlet "+(index+1)), 
                "@anchor "+anchorPrefix+"leftinlet "+split[0], 
                "(Left inlet)", 
                split[1]};
        else if(index < 0)
            return new String[]{
                ("Inlet "+(index*MINUSONE+1)), 
                "@anchor "+anchorPrefix+"rightinlet "+split[0], 
                "(Right inlet)", 
                split[1]};
        else
            return new String[]{
                ("Inlet "+(index+1)),
                split[0], 
                DoxyWriter.HTML_NBSP,
                split[1]};
    }
    
    private String[] outletTextFromIndex(int index, String description)
    { 
        String split[] = StringUtility.splitIOLetTypeDesc(description);
        
        if(index == 0) 
            return new String[]{
                ("Outlet "+(index+1)), 
                "@anchor "+anchorPrefix+"leftoutlet "+split[0], 
                "(Left outlet)", 
                split[1]};
        else if(index < 0)
            return new String[]{
                ("Outlet "+(index*MINUSONE+1)), 
                "@anchor "+anchorPrefix+"rightoutlet "+split[0], 
                "(Right outlet)", 
                split[1]};
        else
            return new String[]{
                ("Outlet "+(index+1)),
                split[0],
                DoxyWriter.HTML_NBSP,
                split[1]};
    }
    
    private String[] polyInletTextFromIndex(int index, String description)
    { 
        String split[] = StringUtility.splitIOLetTypeDesc(description);
        
        if(split[0].equals(HTML_NBSP)) split[0] = "(message)";
        
        if(index == 0) 
            return new String[]{
                ("Inlet "+(index+1)), 
                "@anchor "+anchorPrefix+"leftinlet "+split[0], 
                "(Left inlet)", 
                split[1]};
        else if(index < 0)
            return new String[]{
                ("Inlet "+(index*MINUSONE+1)), 
                "@anchor "+anchorPrefix+"rightinlet "+split[0], 
                "(Right inlet)", 
                split[1]};
        else
            return new String[]{
                ("Inlet "+(index+1)), 
                split[0], 
                HTML_NBSP, 
                split[1]};
    }
    
    private String[] polyOutletTextFromIndex(int index, String description)
    { 
        String split[] = StringUtility.splitIOLetTypeDesc(description);
        
        if(split[0].equals(HTML_NBSP)) split[0] = "(message)";
        
        if(index == 0) 
            return new String[]{
                ("Outlet "+(index+1)), 
                "@anchor "+anchorPrefix+"leftoutlet "+split[0], 
                "(Left outlet)", 
                split[1]};
        else if(index < 0)
            return new String[]{
                ("Outlet "+(index*MINUSONE+1)), 
                "@anchor "+anchorPrefix+"rightoutlet "+split[0], 
                "(Right outlet)", 
                split[1]};
        else
            return new String[]{
                ("Outlet "+(index+1)), 
                split[0], 
                HTML_NBSP, 
                split[1]};
    }
    
    private String[] polySignalInletTextFromIndex(int index, String description)
    { 
        if(index == 0) 
            return new String[]{
                ("Inlet "+(index+1)), 
                "@anchor "+anchorPrefix+"leftinlet (<code>signal</code>)", 
                "(Left inlet)", 
                description};
        else if(index < 0)
            return new String[]{
                ("Inlet "+(index*MINUSONE+1)), 
                "@anchor "+anchorPrefix+"rightinlet (<code>signal</code>)", 
                "(Right inlet)", 
                description};
        else
            return new String[]{
                ("Inlet "+(index+1)), 
                "(<code>signal</code>)", 
                HTML_NBSP, 
                description};
    }
    
    private String[] polySignalOutletTextFromIndex(int index, String description)
    { 
        if(index == 0) 
            return new String[]{
                ("Outlet "+(index+1)), 
                "@anchor "+anchorPrefix+"leftoutlet (<code>signal</code>)", 
                "(Left outlet)", 
                description};
        else if(index < 0)
            return new String[]{
                ("Outlet "+(index*MINUSONE+1)), 
                "@anchor "+anchorPrefix+"rightoutlet (<code>signal</code>)", 
                "(Right outlet)", 
                description};
        else
            return new String[]{
                ("Outlet "+(index+1)), 
                "(<code>signal</code>)", 
                HTML_NBSP, 
                description};
    }
    
    private String[] polyCombinedInletTextFromIndex(int index, String description)
    { 
        if(index == 0) 
            return new String[]{
                ("Inlet "+(index+1)), 
                "@anchor "+anchorPrefix+"leftinlet (<code>signal</code>/<code>message</code>)", 
                "(Left inlet)", 
                description};
        else if(index < 0)
            return new String[]{
                ("Inlet "+(index*MINUSONE+1)), 
                "@anchor "+anchorPrefix+"rightinlet (<code>signal</code>/<code>message</code>)", 
                "(Right inlet)", 
                description};
        else
            return new String[]{
                ("Inlet "+(index+1)), 
                "(<code>signal</code>/<code>message</code>)", 
                HTML_NBSP, 
                description};
    }
    
    private static String[] parseAttrText(String attr)
    {
        String split[];
        
        split = StringUtility.splitAtAttrName(attr);
        if(split.length != 2) return new String[]{ attr, NOTDOCUMENTED };
        String attrName = split[0];
        
        split = StringUtility.splitAtAttrType(split[1]);
        String attrType;
        
        if(split.length != 2) {
            attrType = NOTSPECIFIED;
            attr = split[0];
        } else {
            attrType = split[0];
            attr = split[1];
        }
        
        split = StringUtility.splitAtAttrDesc(attr);
        String attrOptions;
        if(split.length != 2) {
            attrOptions = HTML_NBSP;
        } else {
            attrOptions = split[1];
        }
        
        String attrDesc = split[0];
        
        return new String[]{ 
                attrName, 
                attrType, 
                attrOptions, 
                attrDesc };        
    }
    
    private static String[] parseAttrOptions(String attrOptions)
    {
        return attrOptions.split(FILE_NEWLINE);
    }
    
    private static String[] parseOneAttrOption(String attrOption)
    {
        return StringUtility.splitAttrOption(attrOption);
    }
    
    private static String[] parseArgList(String argList)
    {
        return argList.split(FILE_NEWLINE);
    }
    
    private static String[] parseOneArg(String oneArg)
    {
        String split[] = StringUtility.splitArg(oneArg);
        
        if(split.length != 2)
            return split;
        else {
            String argLabel = split[0];
            String argDesc = split[1];
            
            split = StringUtility.splitTypeDesc(argDesc);
            
            if(!split[0].equals(HTML_NBSP)) {
                argDesc = split[0]+" "+split[1];
            }
            
            return new String[]{ argLabel, argDesc };
        }
    }
    
    private static String[] parseMessageText(String message)
    {
        return parseAttrText(message);
    }
    
    private static String[] parseMessageOptions(String messageOptions)
    {
        return parseAttrOptions(messageOptions);
    }
    
    private static String[] parseOneMessageOption(String messageOption)
    {
        return parseOneAttrOption(messageOption);
    }
    
    private static String parseArgText(String arg)
    {
        if(arg.contains("Argument") || arg.contains("argument")) {
            String split[] = StringUtility.splitAtFirstNewLine(arg);
            
            if(split.length == 2)
                return split[1];
            else
                return arg;
        }
            return arg;
            
    }
    
    static String addCodeTag(String s)
    {
        return HTML_OPENCODE+s+HTML_CLOSECODE;
    }
    
    static String addStrongTag(String s)
    {
        return HTML_OPENSTRONG+s+HTML_CLOSESTRONG;
    }
    
    static String addEmTag(String s)
    {
        return HTML_OPENEM+s+HTML_CLOSEEM;
    }
    
    static String addParen(String s)
    {
        return "("+s+")";
    }
}
